#pragma once
#include <vector>

#define MDL_MINIMIZE 1
#define MDL_MAXIMIZE -1

#define	LOADED	0
#define	OPTIMAL	1
#define	INFEASIBLE	2
#define	INF_OR_UNBD	3
#define	UNBOUNDED	4
#define	CUTOFF	5
#define	ITERATION_LIMIT	6
#define	NODE_LIMIT	7
#define	TIME_LIMIT	8
#define	SOLUTION_LIMIT	9
#define	INTERRUPTED	10
#define	NUMERIC	11
#define	SUBOPTIMAL	12
#define	INPROGRESS	13
#define	USER_OBJ_LIMIT	14
#define	WORK_LIMIT	15
#define	MEM_LIMIT	16
#define	PIVOT	17

namespace sv {
	using matrix = std::vector<std::vector<double>>;
	using rtn = int;
	class Expr;
	class Var;

	enum class IntAttr {
		NumConstrs,
		NumVars,
		NumIntVars,
		NumBinVars,
		ModelSense,
		IsMIP,
		IsMultiObj,
		Status,
		SolCount,
		Lazy,
		NumObj,
		NumCol
	};

	enum class DoubleAttr {
		Runtime,
		Work,
		ObjCon,
		LB,
		UB,
		Obj,
		Start,
		RHS,
		Coeff,
		MaxCoeff,
		MinCoeff,
		MaxBound,
		MinBound,
		ObjVal,
		MIPGap,
		IterCount,
		NodeCount,
		X,
		Slack,
	};

	enum class StringAttr {
		ModelName,
		VarName,
		ConstrName,
		QCName,
		GenConstrName,
		ObjNName,
		ScenNName,
		BatchID,
		VTag,
		CTag,
		QCTag,
		BatchErrorMessage
	};

	enum class ConstrOper {
		LESS_EQUAL,
		GREATER_EQUAL,
		EQUAL
	};


	enum class VarType {
		CONTINUOUS,
		BINARY,
		INTEGER,
	};

	class Var {
	public:
		friend class Model;
		friend class LinSolver;
		friend class Expr;

		Var(double coef = 1, VarType type_ = VarType::CONTINUOUS);
		double get(DoubleAttr attr);
		int get(IntAttr attr);

	private:
		double coeffs;
		double val;
		int col;
		VarType type;
	};

	Expr operator+(const Expr& x, const Expr& y);
	Expr operator-(const Expr& x, const Expr& y);
	Expr operator+(const Expr& x);
	Expr operator+(Var x, Var y);
	Expr operator+(Var x, double a);
	Expr operator+(double a, Var x);
	Expr operator-(const Expr& x);
	Expr operator-(Var x);
	Expr operator-(Var x, Var y);
	Expr operator-(Var x, double a);
	Expr operator-(double a, Var x);
	Expr operator*(double a, Var x);
	Expr operator*(Var x, double a);
	Expr operator*(const Expr& x, double a);
	Expr operator*(double a, const Expr& x);
	Expr operator/(Var x, double a);
	Expr operator/(const Expr& x, double a);


	class Expr
	{
	private:
		double constant;
		std::vector<double> coeffs;
		std::vector<Var> vars;

	public:
		Expr(const Expr& expr) = default;
		Expr(double constant = 0.0);
		Expr(Var var, double coeff = 1.0);

		friend class LinSolver;

		friend Expr operator+(const Expr& x, const Expr& y);
		friend Expr operator+(const Expr& x);
		friend Expr operator+(Var x, Var y);
		friend Expr operator+(Var x, double a);
		friend Expr operator+(double a, Var x);
		friend Expr operator-(const Expr& x, const Expr& y);
		friend Expr operator-(const Expr& x);
		friend Expr operator-(Var x);
		friend Expr operator-(Var x, Var y);
		friend Expr operator-(Var x, double a);
		friend Expr operator-(double a, Var x);
		friend Expr operator*(double a, Var x);
		friend Expr operator*(Var x, double a);
		friend Expr operator*(const Expr& x, double a);
		friend Expr operator*(double a, const Expr& x);
		friend Expr operator/(Var x, double a);
		friend Expr operator/(const Expr& x, double a);


		Expr operator=(const Expr& rhs);
		void operator+=(const Expr& expr);
		void operator-=(const Expr& expr);
		void operator*=(double mult);
		void operator/=(double a);
		Expr operator+(const Expr& rhs);
		Expr operator-(const Expr& rhs);
	};
}
